﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Objects
{
    public abstract class StatePoint<T> : IUndoRedoMan
    {
        protected struct State
        {
            public string PropertyName;
            public string ShowName;
            public Valuehost Value;
            public bool IsContext;
        }

        protected Type TypeObj;
        private T _obj;
        public virtual T obj
        {
            get { return _obj; }
            protected set
            {
                _obj = value;
                TypeObj = _obj.GetType();                
            }
        }

        private Stack<State> undo;
        private Stack<State> redo;

        protected StatePoint(T stateObject)
        {
            undo = new Stack<State>();
            redo = new Stack<State>();
            this.obj = stateObject;
        }

        /// <summary>
        /// Оповещение об обновлении собственного состояния
        /// </summary>
        protected void Refresh()
        {
            OnPropertyChanged("IsUndo");
            OnPropertyChanged("IsRedo");
        }

        /// <summary>
        /// Получить текущее состояние наблюдаемого объекта
        /// </summary>
        /// <param name="propName"></param>
        /// <returns></returns>
        protected abstract State ShotState(string propName);

        /// <summary>
        /// Контракт: index >= 0 && index.LessThan(states.Count)
        /// </summary>
        /// <param name="index"></param>
        protected abstract void SetState(State state);

        #region IStatePoint Members

        public void UndoFull()
        {
            while (Undo()) { }
        }

        public bool Undo()
        {
            if (IsUndo)
            {
                redo.Push(ShotState(undo.Peek().PropertyName));
                SetState(undo.Pop());
                Refresh();
            }
            return IsUndo;
        }

        public void RedoFull()
        {
            while (Redo()) { }
        }

        public bool Redo()
        {
            if (IsRedo)
            {
                undo.Push(ShotState(redo.Peek().PropertyName));
                SetState(redo.Pop());
                Refresh();
            }
            return IsRedo;
        }

        public bool IsUndo
        {
            get { return undo.Count > 0; }
        }

        public bool IsRedo
        {
            get { return redo.Count > 0; }
        }

        #endregion

        #region INotifyPropertyChanged Members

        protected void OnPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            PropertyChanged = null;
            undo.Clear();
            redo.Clear();
            TypeObj = null;
            _obj = default(T);
        }

        #endregion
    }

    public interface IUndoRedo : INotifyPropertyChanged
    {
        IUndoRedoMan GetStatePoint();
    }

    public interface IUndoRedoMan : INotifyPropertyChanged, IDisposable
    {
        /// <summary>
        /// Откат всех операций
        /// </summary>
        void UndoFull();
        /// <summary>
        /// Откат последней операции
        /// </summary>
        bool Undo();
        /// <summary>
        /// Возврат всех изменений
        /// </summary>
        void RedoFull();
        /// <summary>
        /// Возврат следующего действия
        /// </summary>
        bool Redo();
        /// <summary>
        /// Возможность отката
        /// </summary>
        bool IsUndo { get; }
        /// <summary>
        /// Возможность возврата изменений
        /// </summary>
        bool IsRedo { get; }
    }
}
